Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
🦋 Changeset detectedLatest commit: 0d5c695 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Duplicate code analysis5 new clones introduced by this PR.
If one of these duplicates a helper that already exists, import it instead jscpd compared this branch against |
E2E Test Results✅ All tests passed • 466 passed • 1 skipped • 1422s
Tests ran across 4 shards in parallel. |
64ef09d to
7fae5c3
Compare
84db6e4 to
9e1ff09
Compare
9e1ff09 to
b14bc4f
Compare
b14bc4f to
0d5c695
Compare
Summary
Map key=value text index value lookups now run in parallel batches of 10 keys. Indexes with large dictionary blocks read each key separately and return its most frequent values with
topKWeighted, so a high-cardinality key stops early and delays only its own batch. Indexes with small blocks keepmain's query shape within each batch. Plain-column text indexes return the most frequent values withtopKWeighted, in batches of 10 columns.Stacked on #3635, which limits these reads to a configurable set of parts (20 median-sized by default).
The problem
getMapTextIndexKeyValues(Map key=value indexes, e.g.idx_log_attr_items) andgetTextIndexKeyValues(text indexes on plain columns, e.g.TraceId):groupUniqArray(limit)over a sorted dictionary), not the common ones.max_rows_to_read(3M by default) withread_overflow_mode = 'break'. The dictionary is read in sorted token order, so once a high-cardinality key used up the budget, every key that sorts after it came back with no values and no error. On a realLogAttributeslookup, only 22 of 100 keys got values.The fix
topKWeighted(limit)(value, cardinality).cardinalityis the token's row count in the part, so the values are the most frequent, most frequent first.TEXT_INDEX_KEYS_PER_QUERY). They stream in as each query finishes.UNION ALLbranch reading at most 20,000 tokens (TEXT_INDEX_TOKENS_PER_KEY), so a high-cardinality key's read stops early. Nomax_rows_to_readcap ('0', after the team's settings): the per-keyLIMITbounds each read.main's query shape, in queries of at most 10 keys: one read with OR'dstartsWithfilters,groupUniqArray(limit), and themax_rows_to_readcap withread_overflow_mode = 'break'. It keepsmain's known limits: the alphabetically first values, and keys that sort after a high-cardinality key can come back empty. Batches of 10 limit that to at most 9 other keys. The cap is marked with ashortcut:comment.dictionary_block_sizein the index definition, then the table'stext_index_dictionary_block_size, then the server's MergeTree setting, then 512.max_rows_to_readcap either.Why the block size decides
Every read of
mergeTreeTextIndex()opens each part's index and loads its sparse index, which has one entry per dictionary block. SeparateUNION ALLbranches don't share that work.On a local benchmark (1M rows, 3M tokens, 10 parts), a batch of 10 keys:
startsWith)TextIndexReadSparseIndexBlocksThe sparse index was 17.7 KB per part with 512-token blocks and 1.3 KB with 8192. On ClickHouse Cloud, an earlier version with per-key branches on default-block indexes measured about 0.18 CPU-s and 10–30 MiB of cache reads per part per branch (26-part
otel_logs: 804 ms for one key, 772 ms for a key that doesn't exist). With 8192-token blocks that fixed cost is small enough for per-key branches to pay off.Cost and known limits
main. Each opens every part's index once (small blocks) or once per key (large blocks).text_index_dictionary_block_sizekeep their old block size. This only picks the query shape; results are the same either way.topKWeighteduses about 1.6× the CPU per token ofgroupUniqArray. Memory is the same.getEngineSettingmoved fromDBRowTable.tsxtocommon-utils/core/utils.tsso both use one parser forengine_fullsettings.How to reproduce
Starting point:
yarn devagainst ClickHouse 26.3+, with the OTelotel_logsschema (key=value text indexesidx_log_attr_items,idx_res_attr_items), and a Map key with one rare value that sorts first and one common value, e.g.map('freq.key', if(number = 0, 'a-rare', 'z-common'))fromnumbers(4).On
main:freq.keylistsa-rarefirst, andsystem.query_logshows one values query for all keys.On this branch:
dictionary_block_size = 8192).freq.keylistsz-commonfirst.system.query_log: with the seed schema'sdictionary_block_size = 8192, there is one values query per 10 keys, with oneUNION ALLbranch per key. Recreate the index withoutdictionary_block_size(default 512) and each values query hasmain's shape for 10 keys.References